/*
   CLMain.cpp

   Created by Miroslav Karpis on 13/11/11.
   Copyright 2011 Miroslav Karpis. All rights reserved.

   This file is part of molecEvol.

   molecEvol is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Foobar is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "CLMain.h"

#if defined (__APPLE__) || defined(MACOSX)
#define GL_SHARING_EXTENSION "cl_APPLE_gl_sharing"
#else
#define GL_SHARING_EXTENSION "cl_khr_gl_sharing"
#endif

//#define NWITEMS 512
const int ARRAY_SIZE = 1000;


//-------
CLMain::CLMain()
{
    //initialization
    memObjects[0] = 0;
    memObjects[1] = 0;
}


//-------
CLMain::~CLMain()
{

    if (memObjects[0] != 0)
        clReleaseMemObject(memObjects[0]);
    if (memObjects[1] != 0)
        clReleaseMemObject(memObjects[1]);

    if (commandQueue != 0)
        clReleaseCommandQueue(commandQueue);


    if (kernel != 0)
        clReleaseKernel(kernel);

    if (program != 0)
        clReleaseProgram(program);

    if (context != 0)
        clReleaseContext(context);

    delete [] Bij;
}

//-------
bool CLMain::initCL() {

    cl_int errCode;

    //---Initial definition of GARD
    seed = 10000;
    nMolecTypes = 5;
    nTotalBij = nMolecTypes * nMolecTypes;


    //get info about available devices
    if(!getHostDeviceInfo())
        return 1;

    //---G-A-R-D implementation

    //if we have sufficient hardware lets create initial set of molecules
    Bij = new float[nTotalBij];
    gard.initGard(nMolecTypes, Bij);

    for(cl_uint i=0;i<nTotalBij; i++)
        cout << Bij[i] << endl;
    //---enf of G-A-R-D implementation



    // create first assembly
    srand(time(NULL));
    gard.gardAssemb.assemblyID = 1;
    //randomly choose molecule type + number of molecules (maximum is 20)

    for(cl_uint i=0; i<nMolecTypes/2; i++)
    {
        cl_uint rndMolecID = rand() % nMolecTypes;
        gard.gardAssemb.molecules[rndMolecID] =rand() % 20 + 1;
    }

    for(cl_uint i=0;i<nMolecTypes; i++)
        cout << "ID:" << i << "  num:" << gard.gardAssemb.molecules[i] << endl;


    //lets run first simulation time-step of GARD
    if(!gard.runGard(Bij))
        cout<< "problem in runGard timeStep" << endl;

    return 0;

    //create context
    context = 0;
    context = createContext();

    //create command queue
    commandQueue = NULL;
    cl_command_queue_properties queueProps;
    queueProps = CL_QUEUE_PROFILING_ENABLE;
    commandQueue = clCreateCommandQueue(context, devicesIDs[0], queueProps, NULL);
    if (commandQueue == NULL)
    {
        std::cerr << "Failed to create commandQueue for device 0";
        return 1;
    }

    // Create OpenCL program from kernel source
    program = 0;
    kernel = 0;
    cl_event event;

    program = CreateProgram(context, devicesIDs[0], "../kernels/gard.cl");
    if (program == NULL)
        return 1;

    // Create OpenCL kernel
    kernel = clCreateKernel(program, "mainGard", NULL);
    if (kernel == NULL)
    {
        cerr << "Failed to create kernel" << endl;
        return 1;
    }
    else
    {
        // Create output mem. object
        float result[nTotalBij];

        //TODO check if we can use float or unsign float - for saving transfer speed
        memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                                       sizeof(float) * nTotalBij, Bij, NULL);
        memObjects[1] = clCreateBuffer(context, CL_MEM_READ_WRITE,
                                       sizeof(float) * nTotalBij, NULL, NULL);


        // Set the kernel arguments (result, a, b)
        errCode = clSetKernelArg(kernel, 0, sizeof(cl_mem), &memObjects[0]);
        errCode |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &memObjects[1]);
        if (errCode != CL_SUCCESS)
        {
            cerr << "Error setting kernel arguments." << endl;
            return 1;
        }

        size_t numOfWG;
        clGetKernelWorkGroupInfo(kernel,
                                 devicesIDs[0],
                                 CL_KERNEL_WORK_GROUP_SIZE,
                                 sizeof(size_t),
                                 &numOfWG,
                                 NULL);

        std::cout << "numOfWG:" << numOfWG << std::endl;

        size_t globalWorkSize = nTotalBij;
        size_t localWorkSize = 1;

        // Queue the kernel up for execution across the array
        errCode = clEnqueueNDRangeKernel(commandQueue, kernel, 1, NULL,
                                         &globalWorkSize, &localWorkSize,
                                         0, NULL, &event);


        clFinish(commandQueue);
        // lets measure kernel duration time
        if(clWaitForEvents(1, &event) != CL_SUCCESS)
            cerr << "clWaitForEvents error" << endl;

        //------PROFILING START
        long long start, end;
        float total;
        cl_int status;
        status = clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_START,
                                         sizeof start, &start, NULL);
        if (status != CL_SUCCESS)
            cout << "clGetEventProfilingInfo(COMMAND_START) failed" << status << endl;

        status = clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_END,
                                         sizeof end, &end, NULL);
        if (status != CL_SUCCESS)
            cout << "clGetEventProfilingInfo(COMMAND_END) failed" << endl;

        total = (float)(end - start) / 1e6; /* Convert nanoseconds to msecs */
        cout << "Profiling: Total kernel time was (msc):" << total << endl;

        //-------PROFILING END


        //----
        if (errCode != CL_SUCCESS)
        {
            cerr << "Error queuing kernel for execution." << endl;
            return 1;
        }

        // Read the output buffer back to the Host
        errCode = clEnqueueReadBuffer(commandQueue, memObjects[1], CL_TRUE,
                                      0, nTotalBij * sizeof(float), result,
                                      0, NULL, NULL);
        if (errCode != CL_SUCCESS)
        {
            cerr << "Error reading result buffer." << endl;
            return 1;
        }
/*
        // Output the result buffer
        for (int i = 0; i < nTotalBij; i++)
        {
            cout << result[i] << endl;
        }
  */
      cout << std::endl;
        cout << "Executed program succesfully." <<  endl;

        //----------

    }


    /*
    //check if our device supports context sharing
    clGetDeviceInfo(devicesIDs[0], CL_DEVICE_EXTENSIONS, 0, NULL, &extensionSize );
    if(extensionSize > 0)
    {
        size_t extensionSize;
        clGetDeviceInfo(devicesIDs[0], CL_DEVICE_EXTENSIONS, 0, NULL, &extensionSize );
        if(extensionSize > 0)
        {
            char* extensions = (char*)malloc(extensionSize);
            clGetDeviceInfo(devicesIDs[0], CL_DEVICE_EXTENSIONS, extensionSize, extensions, &extensionSize);

            cout << "\t Device extensions: " << extensions << endl;
            std::string stdDevString(extensions);
            free(extensions);

            size_t szOldPos = 0;
            size_t szSpacePos = stdDevString.find(' ', szOldPos); // extensions string is space delimited

            {

                if( strcmp(GL_SHARING_EXTENSION, stdDevString.substr(szOldPos, szSpacePos - szOldPos).c_str()) == 0 )
                    // Device supports context sharing with OpenGL
                    bSharingSupported = true;

                do
                {
                    szOldPos = szSpacePos + 1;
                    szSpacePos = stdDevString.find(' ', szOldPos);
                }
                while (szSpacePos == szOldPos);
            }
        }
    }
*/
    /*
    //------create context
    cl_context context;
    context = clCreateContext(0, 1, devicesIDs, NULL, NULL, &errCode);
    if (!context){
        cout << "Error: Failed to create a compute context!\n";
        return false;
    }


    cl_command_queue queue = clCreateCommandQueue( context, devicesIDs[0],
                                                   0, NULL );


    // Create OpenCL program from HelloWorld.cl kernel source
    cl_program program = CreateProgram(context, devicesIDs[0], "base.cl");
    if (program == NULL)
    {
        cout << "Error: Failed to compile source!\n";
        return false;
    }
    
    cl_kernel kernel = clCreateKernel( program, "memset", NULL );

    
    // 5. Create a data buffer.
    cl_mem buffer = clCreateBuffer( context, CL_MEM_WRITE_ONLY,
                                    NWITEMS * sizeof(cl_uint), NULL, NULL );
    // 6. Launch the kernel. Let OpenCL pick the local work size.
    size_t global_work_size = NWITEMS;
    clSetKernelArg(kernel, 0, sizeof(buffer), (void*) &buffer);
    clEnqueueNDRangeKernel( queue, kernel, 1, NULL, &global_work_size, NULL, 0, NULL, NULL);

    clFinish( queue );
    // 7. Look at the results via synchronous buffer map.
    cl_uint *ptr;
    ptr = (cl_uint *) clEnqueueMapBuffer( queue, buffer, CL_TRUE, CL_MAP_READ, 0,
                                          NWITEMS * sizeof(cl_uint), 0, NULL, NULL, NULL );

    int i;
    for(i=0; i < NWITEMS; i++) printf("%d %d\n", i, ptr[i]);
*/
    /*
 // Define OS-specific context properties and create the OpenCL context
#if defined (__APPLE__)
 CGLContextObj kCGLContext = CGLGetCurrentContext();
 CGLShareGroupObj kCGLShareGroup = CGLGetShareGroup(kCGLContext);
 cl_context_properties props[] =
 {
  CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE, (cl_context_properties)kCGLShareGroup,
  0
 };
 context = clCreateContext(props, 0,0, NULL, NULL, &errCode);
#else
#ifdef UNIX
 cl_context_properties props[] =
 {
  CL_GL_CONTEXT_KHR, (cl_context_properties)glXGetCurrentContext(),
  CL_GLX_DISPLAY_KHR, (cl_context_properties)glXGetCurrentDisplay(),
  CL_CONTEXT_PLATFORM, (cl_context_properties)cpPlatform,
  0
 };
 cxGPUContext = clCreateContext(props, 1, &cdDevices[uiDeviceUsed], NULL, NULL, &ciErrNum);
#else // Win32
 cl_context_properties props[] =
 {
  CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(),
  CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(),
  CL_CONTEXT_PLATFORM, (cl_context_properties)cpPlatform,
  0
 };
 cxGPUContext = clCreateContext(props, 1, &cdDevices[uiDeviceUsed], NULL, NULL, &ciErrNum);
#endif
#endif

 */
    delete [] devicesIDs;
    delete [] clPlatformIDs;

    return true;
}




